#include <vector>
#include "MapReader.h"
#include "BinaryReader.h"
#include "BitstreamReader.h"

namespace opennav {

MapReader::MapReader(FileInterface* inputfile, Map* map) : inputfile_(inputfile), map_(map) {
	BinaryReader reader(inputfile_);
	reader.read_int32(offset_map_tiles_);
	reader.read_int32(offset_road_);
	reader.read_int32(offset_search_);
	reader.read_int32(offset_label_);
	offset_location_ = sizeof(int32_t) * 4;
	read_location_header();
	read_map_tile_header();
	read_road_header();
	read_search_header();
}

/*
 * Location Subfile Section
 */

const LocationHeader& MapReader::location_header() const {
	return header_location_;
}

void MapReader::location_country_entry(uint8_t country_id, Country& country) {
	int32_t offset = offset_location_ + header_location_.country_table_offset + country_id * 4;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	country.country_id = country_id;
	int32_t label_offset = reader.read_int32();
	country.name = load_label(label_offset);
}

void MapReader::location_region_entry(int16_t region_id, Region& region) {
	int32_t offset = offset_location_ + header_location_.region_table_offset + region_id * 7;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	region.region_id = region_id;
	int32_t label_offset = reader.read_int32();
	region.country_id = reader.read_uint8();
	region.parent_region_id = reader.read_int16();
	region.name = load_label(label_offset);
}

void MapReader::location_city_entry(int32_t city_id, City& city) {
	int32_t offset = offset_location_ + header_location_.city_table_offset + city_id * 13;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	city.city_id = city_id;
	int32_t label_offset = reader.read_int32();
	city.region_id = reader.read_int16();
	city.parent_city_id = reader.read_int24();
	city.map_object_ref.object_id = reader.read_int16();
	city.map_object_ref.tile_id = reader.read_uint16();
	city.name = load_label(label_offset);
}

void MapReader::read_location_header() {
	inputfile_->seek(offset_location_);
	BinaryReader reader(inputfile_);
	header_location_.country_table_offset = reader.read_int32();
	header_location_.country_table_size = reader.read_uint8();
	header_location_.region_table_offset = reader.read_int32();
	header_location_.region_table_size = reader.read_int16();
	header_location_.city_table_offset = reader.read_int32();
	header_location_.city_table_size = reader.read_int24();
}

/*
 * MapTile Subfile Section
 */

const MapTileHeader& MapReader::map_tile_header() const {
	return header_map_tiles_;
}
void MapReader::zoom_table_entry(uint8_t level, Zoom& zoom) {
	int32_t offset = offset_map_tiles_ + header_map_tiles_.zoom_table_offset + level * 3;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	zoom.zoom_level = level;
	zoom.resolution = reader.read_uint8();
	zoom.tiles = reader.read_uint16();
}

void MapReader::zoom_table_entries(std::vector<Zoom>& zoom_levels) {
	for (uint8_t level = 0; level < header_map_tiles_.zoom_table_size; level++) {
		Zoom zoom;
		zoom_table_entry(level, zoom);
		zoom_levels.push_back(zoom);
	}
}

void MapReader::map_tile_def_table_entry(uint16_t tile_id, MapTileDef& tile_def) {
	int32_t offset = offset_map_tiles_ + header_map_tiles_.map_tile_def_table_offset + tile_id * 17;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	tile_def.tile_id = tile_id;
	tile_def.center.longitude = reader.read_int24() << 8;
	tile_def.center.latitude = reader.read_int24() << 8;
	uint8_t zoom_level = reader.read_uint8();
	int32_t width = reader.read_uint16();
	int32_t height = reader.read_uint16();
	tile_def.first_subtile_id = reader.read_uint16();
	uint32_t off = reader.read_uint32();
	tile_def.tile_offset = off & 0x7fffffff;
	tile_def.last = ((off & 0x80000000) > 0 ? true : false);
	zoom_table_entry(zoom_level, tile_def.zoom);
	width = width << (tile_def.zoom.shift() + 8);
	height = height << (tile_def.zoom.shift() + 8);
	tile_def.bounds = Area(tile_def.center, width, height);
}

void MapReader::map_tile_def_table_entries(std::vector<MapTileDef>& tile_defs) {
	MapTileDef current_tile_def;
	for (uint16_t tile_id = 0; tile_id < header_map_tiles_.map_tile_def_table_size; tile_id++) {
		MapTileDef tile_def;
		map_tile_def_table_entry(tile_id, tile_def);
		tile_defs.push_back(tile_def);
	}
}

void MapReader::map_tile_table_entry(const MapTileDef& tile_def, std::vector<MapObject*>& tile_content) {
	int32_t offset = offset_map_tiles_ + header_map_tiles_.map_tile_table_offset + tile_def.tile_offset;
	inputfile_->seek(offset);
	uint8_t shift = tile_def.zoom.shift();
	int32_t center_longitude = tile_def.center.longitude >> 8;
	int32_t center_latitude = tile_def.center.latitude >> 8;
	BinaryReader reader(inputfile_);
	int16_t object_id = 0;
	uint16_t object_d;
	do {
		object_d = reader.read_uint16();
		bool has_ref = (object_d & 0x2000) > 0 ? true : false;
		switch (object_d & 0xC000) {
		case 0x4000:
			{
				MapPoint* point = new MapPoint();
				point->map = map_;
				point->point_type = object_d & 0xfff;
				point->reference = -1;
				point->poi = false;
				if (has_ref) {
					uint32_t reference = reader.read_uint32();
					point->reference = reference & 0x7fffffff;
					point->poi = ((reference & 0x80000000) > 0 ? true : false);
				}
				point->coordinate.longitude = center_longitude + (reader.read_int16() << shift);
				point->coordinate.latitude = center_latitude + (reader.read_int16() << shift);
				point->map_object_ref.object_id = object_id;
				point->map_object_ref.tile_id = tile_def.tile_id;
				point->bounds.add_to_bounds(point->coordinate);
				tile_content.push_back(point);
			}
			break;
		case 0x8000:
			{
				MapLine* line = new MapLine();
				line->map = map_;
				line->line_type = object_d & 0xfff;
				line->reference = -1;
				line->road = false;
				if (has_ref) {
					uint32_t reference = reader.read_uint32();
					line->reference = reference & 0x7fffffff;
					line->road = ((reference & 0x80000000) > 0 ? true : false);
				}
				int32_t longitude = center_longitude + (reader.read_int16() << shift);
				int32_t latitude = center_latitude + (reader.read_int16() << shift);
				uint16_t bitstream_len = reader.read_uint8();
				if (bitstream_len & 0x80) bitstream_len = ((bitstream_len & 0x7f) << 8) + reader.read_uint8();
				uint8_t bitstream[bitstream_len];
				reader.read(&bitstream, bitstream_len);
				BitstreamReader bitstream_reader(bitstream, bitstream_len);
				int8_t base_longitude = bitstream_reader.read(4);
				int8_t base_latitude = bitstream_reader.read(4);
				bool fixed_lon = bitstream_reader.read_bit();
				bool sign_lon = (fixed_lon ? bitstream_reader.read_bit() : false);
				bool fixed_lat = bitstream_reader.read_bit();
				bool sign_lat = (fixed_lat ? bitstream_reader.read_bit() : false);
				int32_t clen = base_longitude + base_latitude + (fixed_lon ? 0 : 1) + (fixed_lat ? 0 : 1);
				if (clen > 0) {
					int32_t clon = longitude >> shift, clat = latitude >> shift;
					int32_t ncoords = bitstream_reader.bits_remaining() / clen;
					line->coordinates.push_back(Coordinate((clon << shift) << 8, (clat << shift) << 8));
					for (int32_t c = 1; c <= ncoords; c++) {
						if (!fixed_lon) sign_lon = bitstream_reader.read_bit();
						int32_t dlon = bitstream_reader.read(base_longitude);
						if (!fixed_lat) sign_lat = bitstream_reader.read_bit();
						int32_t dlat = bitstream_reader.read(base_latitude);
						if (sign_lon) dlon = -dlon;
						if (sign_lat) dlat = -dlat;
						clon = clon + dlon;
						clat = clat + dlat;
						line->coordinates.push_back(Coordinate((clon << shift) << 8, (clat << shift) << 8));
					}
				}
				line->bounds.add_to_bounds(line->coordinates);
				line->map_object_ref.object_id = object_id;
				line->map_object_ref.tile_id = tile_def.tile_id;
				tile_content.push_back(line);
			}
			break;
		case 0xC000:
			{
				MapShape* shape = new MapShape();
				shape->map = map_;
				shape->shape_type = object_d & 0xfff;
				shape->reference = -1;
				if (has_ref) {
					uint32_t reference = reader.read_uint32();
					shape->reference = reference & 0x7fffffff;
				}
				int32_t longitude = center_longitude + (reader.read_int16() << shift);
				int32_t latitude = center_latitude + (reader.read_int16() << shift);
				uint16_t bitstream_len = reader.read_uint8();
				if (bitstream_len & 0x80) bitstream_len = ((bitstream_len & 0x7f) << 8) + reader.read_uint8();
				uint8_t bitstream[bitstream_len];
				reader.read(&bitstream, bitstream_len);
				BitstreamReader bitstream_reader(bitstream, bitstream_len);
				int8_t base_longitude = bitstream_reader.read(4);
				int8_t base_latitude = bitstream_reader.read(4);
				bool fixed_lon = bitstream_reader.read_bit();
				bool sign_lon = (fixed_lon ? bitstream_reader.read_bit() : false);
				bool fixed_lat = bitstream_reader.read_bit();
				bool sign_lat = (fixed_lat ? bitstream_reader.read_bit() : false);
				int32_t clen = base_longitude + base_latitude + (fixed_lon ? 0 : 1) + (fixed_lat ? 0 : 1);
				if (clen > 0) {
					int32_t clon = longitude >> shift, clat = latitude >> shift;
					int32_t ncoords = bitstream_reader.bits_remaining() / clen;
					shape->coordinates.push_back(Coordinate((clon << shift) << 8, (clat << shift) << 8));
					for (int32_t c = 1; c <= ncoords; c++) {
						if (!fixed_lon) sign_lon = bitstream_reader.read_bit();
						int32_t dlon = bitstream_reader.read(base_longitude);
						if (!fixed_lat) sign_lat = bitstream_reader.read_bit();
						int32_t dlat = bitstream_reader.read(base_latitude);
						if (sign_lon) dlon = -dlon;
						if (sign_lat) dlat = -dlat;
						clon = clon + dlon;
						clat = clat + dlat;
						shape->coordinates.push_back(Coordinate((clon << shift) << 8, (clat << shift) << 8));
					}
				}
				shape->bounds.add_to_bounds(shape->coordinates);
				shape->map_object_ref.object_id = object_id;
				shape->map_object_ref.tile_id = tile_def.tile_id;
				tile_content.push_back(shape);
			}
			break;
		}
		object_id++;
	} while ((object_d & 0x1000) == 0);
}

void MapReader::read_map_tile_header() {
	inputfile_->seek(offset_map_tiles_);
	BinaryReader reader(inputfile_);
	header_map_tiles_.zoom_table_offset = reader.read_int32();
	header_map_tiles_.zoom_table_size = reader.read_uint8();
	header_map_tiles_.map_tile_def_table_offset = reader.read_int32();
	header_map_tiles_.map_tile_def_table_size = reader.read_uint16();
	header_map_tiles_.map_tile_table_offset = reader.read_int32();
}

/*
 * Road Subfile Section
 */

const RoadHeader& MapReader::road_header() const {
	return header_road_;
}

void MapReader::road_road_entry(int32_t id, Road& road) {
	int32_t offset_index = offset_road_ + header_road_.road_def_index_table_offset + id * 4;
	inputfile_->seek(offset_index);
	BinaryReader reader(inputfile_);
	int32_t offset_road = offset_road_ + header_road_.road_def_table_offset + reader.read_int32();
	inputfile_->seek(offset_road);
	road.road_id = id;
	uint8_t flags = reader.read_uint8();
	std::vector<int32_t> label_offsets;
	if (flags & 0x80) {
		uint32_t label;
		do {
			label = reader.read_uint32();
			label_offsets.push_back(label & 0x7fffffff);
		} while ((label & 0x80000000) == 0);
	}
	road.city_id = -1;
	if (flags & 0x40) {
		road.city_id = reader.read_int24();
	}
	road.segments_offset = reader.read_int32();

	road.labels.clear();
	for (std::vector<int32_t>::iterator it = label_offsets.begin(); it != label_offsets.end(); it++) {
		road.labels.push_back(load_label(*it));
	}
}

void MapReader::road_segment_entry(int32_t offset, RoadSegment& segment) {
	int32_t offset_segment = offset_road_ + header_road_.segment_table_offset + offset;
	inputfile_->seek(offset_segment);
	BinaryReader reader(inputfile_);
	uint8_t flags = reader.read_uint8();
	segment.offset = offset;
	segment.last = ((flags & 0x80) > 0 ? true : false);
	segment.oneway = ((flags & 0x40) > 0 ? true : false);
	segment.backward = ((flags & 0x20) > 0 ? true : false);
	segment.toll = ((flags & 0x10) > 0 ? true : false);
	segment.type = (RoadType)(flags & 0xf);
	segment.road_id = reader.read_int24();
	segment.junction_from_offset = reader.read_int32();
	segment.junction_to_offset = reader.read_int32();
	segment.maxspeed = reader.read_uint8();
	segment.length = reader.read_uint16();
	uint16_t object;
	segment.map_object_refs.clear();
	do {
		object = reader.read_uint16();
		int16_t object_id = (int16_t)(object & 0x7fff);
		uint16_t tile_id = reader.read_uint16();
		MapObjectReference ref;
		ref.tile_id = tile_id;
		ref.object_id = object_id;
		segment.map_object_refs.push_back(ref);
	} while ((object & 0x8000) == 0);
}

void MapReader::road_segment_entries(int32_t offset, std::vector<RoadSegment>& segments) {
	do {
		RoadSegment current_segment;
		road_segment_entry(offset, current_segment);
		segments.push_back(current_segment);
		inputfile_->position(offset);
		offset -= offset_road_;
		offset -= header_road_.segment_table_offset;
		if (current_segment.last == true) break;
	} while(true);
}

void MapReader::road_junction_entry(int32_t offset, RoadJunction& junction) {
	int32_t offset_junction = offset_road_ + header_road_.junction_table_offset + offset;
	inputfile_->seek(offset_junction);
	BinaryReader reader(inputfile_);
	junction.offset = offset;
	uint8_t flags = reader.read_uint8();
	int32_t longitude = reader.read_int24();
	int32_t latitude = reader.read_int24();
	uint32_t segment;
	junction.segment_offsets.clear();
	do {
		segment = reader.read_uint32();
		junction.segment_offsets.push_back(segment & 0x7fffffff);
	} while((segment & 0x80000000) == 0);
	junction.coordinate = Coordinate(longitude << 8, latitude << 8);
}

void MapReader::read_road_header() {
	inputfile_->seek(offset_road_);
	BinaryReader reader(inputfile_);
	header_road_.road_def_index_table_offset = reader.read_int32();
	header_road_.road_def_index_table_size = reader.read_uint24();
	header_road_.road_def_table_offset = reader.read_int32();
	header_road_.segment_table_offset = reader.read_int32();
	header_road_.junction_table_offset = reader.read_int32();
}

/*
 * Label Subfile Section
 */
std::string MapReader::load_label(int32_t offset) const {
	int32_t offset_label = offset_label_ + offset;
	inputfile_->seek(offset_label);
	BinaryReader reader(inputfile_);
	uint16_t len = reader.read_uint16();
	int8_t* label_data = new int8_t[len + 1];
	label_data[len] = '\0';
	reader.read(label_data, len);
	std::string label((const char*)label_data);
	return label;
}

/*
 * Search Subfile Section
 */

const SearchHeader& MapReader::search_header() const {
	return header_search_;
}

void MapReader::search_sorted_country_table_entry(uint8_t entry_id, SortedCountryTableEntry& entry) {
	int32_t offset = offset_search_ + header_search_.country_tables_offset + entry_id * 1;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	entry.id = entry_id;
	entry.country_id = reader.read_uint8();
}

void MapReader::search_country_to_city_table_entry(uint8_t entry_id, CountryToCityTableEntry& entry) {
	int32_t offset = offset_search_ + header_search_.country_tables_offset + header_search_.country_tables_size * 1 + entry_id * 3;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	entry.id = entry_id;
	entry.sorted_city_table_entry_id = reader.read_uint24();
}

void MapReader::search_sorted_city_table_entry(int32_t entry_id, SortedCityTableEntry& entry) {
	int32_t offset = offset_search_ + header_search_.city_tables_offset + entry_id * 3;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	entry.id = entry_id;
	entry.city_id = reader.read_uint24();
}

void MapReader::search_city_to_road_table_entry(int32_t entry_id, CityToRoadTableEntry& entry) {
	int32_t offset = offset_search_ + header_search_.city_tables_offset + header_search_.city_tables_size * 3 + entry_id * 4;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	entry.id = entry_id;
	entry.sorted_road_table_entry_id = reader.read_int32();
}

void MapReader::search_sorted_road_table_entry(int32_t entry_id, SortedRoadTableEntry& entry) {
	int32_t offset = offset_search_ + header_search_.road_tables_offset + entry_id * 4;
	inputfile_->seek(offset);
	BinaryReader reader(inputfile_);
	entry.id = entry_id;
	entry.label_id = reader.read_uint8();
	entry.road_id = reader.read_uint24();
}

void MapReader::read_search_header() {
	inputfile_->seek(offset_search_);
	BinaryReader reader(inputfile_);
	header_search_.country_tables_offset = reader.read_int32();
	header_search_.country_tables_size = reader.read_uint8();
	header_search_.city_tables_offset = reader.read_int32();
	header_search_.city_tables_size = reader.read_uint24();
	header_search_.road_tables_offset = reader.read_int32();
	header_search_.road_tables_size = reader.read_int32();
}

}
